package com.github.shiqiyue.rate.limiter;

import com.github.shiqiyue.rate.limiter.dao.RateLimiterDao;
import com.github.shiqiyue.rate.limiter.entity.RateLimiterItem;
import com.github.shiqiyue.rate.limiter.intercept.action.RateLimiterInterceptAction;
import com.github.shiqiyue.rate.limiter.redis.name.strategy.RedisKeyNameStrategy;
import com.github.shiqiyue.rate.limiter.utils.IpUtils;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.TimeUnit;

/***
 * 实现流量控制动作
 * 
 * @author wwy
 *
 */
public abstract class AbstractRateLimiterAction {
	protected RedisKeyNameStrategy redisKeyNameStrategy;
	
	protected RateLimiterDao flowControlDao;
	
	protected RateLimiterInterceptAction flowControlInterceptAction;
	
	protected RedissonClient redissonClient;
	
	public AbstractRateLimiterAction() {
	}
	
	public AbstractRateLimiterAction(RateLimiterConfigurer configurer) {
		redisKeyNameStrategy = configurer.redisKeyNameStrategy();
		flowControlDao = configurer.flowControlDao();
		flowControlInterceptAction = configurer.flowControlInterceptAction();
		redissonClient = configurer.redissonClient();
		
	}
	
	/***
	 * 执行流量控制操作
	 * 
	 * @param request
	 * @param response
	 * @return
	 */
	public boolean doRateLimit(HttpServletRequest request, HttpServletResponse response) {
		boolean canAccess = true;
		// 访问地址
		String path = request.getServletPath();
		// 获取符合访问控制的数据项
		List<RateLimiterItem> items = flowControlDao.matchItems(path);
		if (items == null || items.isEmpty()) {
			return true;
		}
		String shortestExpl = "";
		for (RateLimiterItem item : items) {
			if (item.getExpl().length() > shortestExpl.length()) {
				shortestExpl = item.getExpl();
			}
		}
		// 获取ip
		String ip = IpUtils.getIpAddr(request);
		String lockName = redisKeyNameStrategy.getLockKeyName(ip, shortestExpl);
		RLock lock = redissonClient.getReadWriteLock(lockName).writeLock();
		lock.expire(5000, TimeUnit.MILLISECONDS);
		lock.lock();
		// 判断是否能够访问
		for (RateLimiterItem item : items) {
			String key = redisKeyNameStrategy.getKeyName(ip, item.getExpl(), item.getId());
			// 访问次数
			RAtomicLong callTimes = redissonClient.getAtomicLong(key);
			if (!callTimes.isExists()) {
				callTimes.set(0L);
				callTimes.expire(item.getExpires(), TimeUnit.MILLISECONDS);
			}
			if (callTimes.get() + 1 > item.getMaxCallTimes()) {
				canAccess = false;
				break;
			}
		}
		if (!canAccess) {
			flowControlInterceptAction.action(request, response);
		} else {
			// 可以访问，访问次数增加
			for (RateLimiterItem item : items) {
				String key = redisKeyNameStrategy.getKeyName(ip, item.getExpl(), item.getId());
				// 访问次数
				RAtomicLong callTimes = redissonClient.getAtomicLong(key);
				callTimes.incrementAndGet();
			}
		}
		lock.unlock();
		return canAccess;
		
	}
}
